// get file size
owl_umax owl_get_file_size(owl_byte * file_path)
{
  // check params
  if (file_path == NULL)
    goto err;
  
  // variable to return
  owl_umax size = 0;
  owl_smax tmp = 0;
  FILE * f = fopen((void *) file_path, "rb");
  if (f == NULL) goto err;
  while ((tmp = fgetc(f)) != EOF) size++;
  
  // done!
  fclose(f);
  return size;
  err:
  return 0;
}

// check pointer
owl_bool owl_check_file_pointer(owl_byte * src, owl_umax size)
{
  return (src != NULL && size >= sizeof(owl_file));
}

// check members
owl_bool owl_check_file_members(owl_str * path, owl_str * data)
{
  return (owl_check_str_all(path, sizeof(owl_str))
          && owl_check_str_all(data, sizeof(owl_str)));
}

// check all
owl_bool owl_check_file_all(owl_file * f, owl_umax size)
{
  return owl_check_file_pointer((void *) f, size) 
         && owl_check_file_members(f -> path, f -> data);
}

// create
owl_file * owl_create_file(owl_byte * file_path, owl_umax path_size,
                           owl_wchar_enc path_enc, owl_wchar_enc file_enc)
{
  // check params
  owl_str * path = NULL, * data = NULL;
  owl_mem_space * path_sp = NULL, * data_sp = NULL;
  owl_file * f = NULL;
  FILE * tmp = NULL;
  if (owl_check_ptr(file_path, path_size) == owl_false
      || owl_check_wchar_enc(path_enc) == owl_false
      || owl_check_wchar_enc(file_enc) == owl_false)
    goto err;
  
  // get path string
  path_sp = owl_create_mem_space(path_size + 1);
  if (path_sp == NULL) goto err;
  owl_copy_bytarr(&((owl_bytarr) {file_path, path_size}), (owl_bytarr *) path_sp);
  path = owl_create_str(path_sp -> ptr, path_sp -> size - 1, path_enc); // lie on the size
  if (path == NULL) goto err;
  
  // get data string
  // load the whole file into memory
  owl_umax file_size = owl_get_file_size((void *) path_sp -> ptr);
  if (file_size == 0) goto err;
  data_sp = owl_create_mem_space(file_size);
  if (data_sp == NULL) goto err;
  owl_smax tmp1 = 0;
  tmp = fopen((void *) path_sp -> ptr, "rb");
  if (tmp == NULL) goto err;
  for (owl_umax i = 0; (tmp1 = fgetc(tmp)) != EOF; i++)
    (data_sp -> ptr)[i] = (owl_byte) tmp1;
  data = owl_create_str(data_sp -> ptr, data_sp -> size, file_enc);
  if (data == NULL) goto err;
  
  // assign the space to the file structure
  f = (void *) owl_create_mem_space(sizeof(owl_file)) -> ptr;
  if (f == NULL) goto err;
  
  // assign the variables of the struct
  f -> path = path; 
  f -> data = data; 
  
  // done!
  owl_free_mem_space(path_sp);
  owl_free_mem_space(data_sp);
  fclose(tmp);
  return f;
  err: // error
  owl_free_str(path);
  owl_free_str(data);
  owl_free_mem_space(path_sp);
  owl_free_mem_space(data_sp);
  owl_free_mem_space(&((owl_mem_space){(void *) f, sizeof(owl_file)}));
  if (tmp != NULL) fclose(tmp);
  return NULL;
}

// free
owl_umax owl_free_file(owl_file * file)
{
  // check params
  if (owl_check_file_all(file, sizeof(owl_file)) == owl_false)
    goto err;
  // free the memory allocated by path and data struct
  owl_umax tmp = owl_free_str(file -> path);
  if (tmp == 0) goto err;
  tmp = owl_free_str(file -> data);
  if (tmp == 0) goto err;
  // free the memory allocated by the struct
  tmp = owl_free_mem_space(&((owl_mem_space) {(void *) file, sizeof(owl_file)}));
  if (tmp == 0) goto err;  
  // return the number of free slots on the mem_space table
  return tmp;
  err:
  return 0;
}

// print
owl_umax owl_print_file(owl_byte * src, owl_umax size)
{
  // check params
  printf("OWL_FILE struct info:\n");
  printf("  Struct info: %p, %llu\n", src, size);
  if (owl_check_file_all((void *) src, sizeof(owl_file)) == owl_false)
    return 0;
  // else print the structure
  owl_file * file = (void *) src;
  printf("  Path info: %p, %llu, %s\n", file -> path -> arr -> ptr,
                                       file -> path -> arr -> size,
                                       OWL_WCHAR_ENC_STR[file -> path -> enc]);
  printf("  Path characters (hex): ");
  for (owl_umax i = 0; i < (file -> path -> arr -> size); putchar(' '))
    i += file -> path -> print_wchar_enc(file -> path -> arr -> ptr + i,
                                         file -> path -> arr -> size - i);
  putchar('\n');
  printf("  Data info: %p, %llu, %s\n", file -> data -> arr -> ptr,
                                       file -> data -> arr -> size,
                                       OWL_WCHAR_ENC_STR[file -> data -> enc]);
  printf("  Data characters (hex): ");
  for (owl_umax i = 0; i < (file -> data -> arr -> size); putchar(' '))
    i += file -> data -> print_wchar_enc(file -> data -> arr -> ptr + i,
                                         file -> data -> arr -> size - i);
  putchar('\n');
  return sizeof(owl_file);
}

// compare
owl_char owl_comp_file(owl_byte * file1, owl_byte * file2)
{
  // check params
  owl_file * file1_tmp = (void *) file1,
          * file2_tmp = (void *) file2;
  if (owl_check_file_all(file1_tmp, sizeof(owl_file)) == owl_false
      || owl_check_file_all(file2_tmp, sizeof(owl_file)) == owl_false)
      return owl_comp_err;
  
  // compare byte contents and then if equal compare paths
  owl_char cmp = owl_comp_str((void *) file1_tmp -> data, (void *) file2_tmp -> data);
  if (cmp != owl_comp_equal) return cmp;
  return owl_comp_str((void *) file1_tmp -> path, (void *) file2_tmp -> path);
}

// get extension of a file (including the starting dot)
owl_str * owl_get_file_ext(owl_file * file)
{
  // check params
  if (owl_check_file_all(file, sizeof(owl_file)) == owl_false)
    return NULL;
  
  // an extension is both the dot and at least a single character
  // a dot and nothing else is not an extension (at least for me)
  owl_umax last_ch_index = owl_get_str_ch_length(file -> path) - 1;
  owl_wchar ch = 0;
  owl_umax sub_size = 0;
  for (owl_umax i = 0; i < last_ch_index; i++) {
    ch = file -> path -> get_wchar(owl_str_ch_at_index(file -> path, last_ch_index - i),
                                   file -> path -> arr -> size);
    sub_size += file -> path -> check_wchar(ch);
    // stop reading if a path divisor is found
    if (ch == (file -> path -> path)[OWL_WCHAR_PATH_DIV_LIN]
        || ch == (file -> path -> path)[OWL_WCHAR_PATH_DIV_WIN])
      break;
    // found it mate
    if (ch == (file -> path -> path)[OWL_WCHAR_PATH_FILE_EXT])
      return owl_create_str(file -> path -> arr -> ptr + file -> path -> arr -> size - sub_size,
                            sub_size, file -> path -> enc);
  }
  
  // nothing was found
  return NULL;
}

// get the base path of a file
owl_str * owl_get_file_basepath(owl_file * file)
{
  // check params
  if (owl_check_file_all(file, sizeof(owl_str)) == owl_false)
    return NULL;
  
  // get the basepath
  owl_umax last_ch_index = owl_get_str_ch_length(file -> path) - 1;
  owl_wchar ch = 0;
  for (owl_umax i = 0; i < file -> path -> arr -> size; /**/) {
    ch = file -> path -> get_wchar(owl_str_ch_at_index(file -> path, last_ch_index - i),
                                   file -> path -> arr -> size);
    // found it
    if (ch == (file -> path -> path)[OWL_WCHAR_PATH_DIV_LIN]
        || ch == (file -> path -> path)[OWL_WCHAR_PATH_DIV_WIN])
      return owl_create_str(file -> path -> arr -> ptr,
                            file -> path -> arr -> size - i,
                            file -> path -> enc);
    i += file -> path -> check_wchar(ch);
  }
  
  // no slashes found, return "./" (fuck windows)
  owl_wchar tmp[] = {(file -> path -> path)[OWL_WCHAR_PATH_MOD_LIN],
                     (file -> path -> path)[OWL_WCHAR_PATH_DIV_LIN]};
  return owl_encode_wchars(tmp, 2, file -> path -> enc);
}

// get the name of a file
owl_str * owl_get_file_name(owl_file * file)
{
  // check params
  if (owl_check_file_all(file , sizeof(owl_file)) == owl_false)
    return NULL;
  
  // find first dot (without reading slashes)
  // until first slash
  // read from right to left
  
  // check if an extension exists
  owl_bool ext_begin_found = owl_false;
  owl_str * tmp = owl_get_file_ext(file);
  if (tmp == NULL) ext_begin_found = owl_true;
  owl_free_str(tmp);
  
  // inspect the string
  owl_umax last_ch_index = owl_get_str_ch_length(file -> path) - 1;
  owl_wchar ch = 0;
  owl_byte * ptr = NULL;
  owl_umax size = 0;
  for (owl_umax i = 0; i < file -> path -> arr -> size; /**/) {
    ptr = owl_str_ch_at_index(file -> path, last_ch_index - i);
    ch = file -> path -> get_wchar(ptr, file -> path -> arr -> size);
    // find the extension dot
    if (ext_begin_found == owl_false) {
      if (ch == (file -> path -> path)[OWL_WCHAR_PATH_FILE_EXT])
        ext_begin_found = owl_true;
    } else {
      // first slash found after the dot ends the filename
      if (ch == (file -> path -> path)[OWL_WCHAR_PATH_DIV_LIN]
          || ch == (file -> path -> path)[OWL_WCHAR_PATH_DIV_WIN]) {
        ptr += file -> path -> check_wchar(ch);
        goto outside;
      }
      // increase the final string size
      size += file -> path -> check_wchar(ch);
    }
    i += file -> path -> check_wchar(ch);
  }
  
  // return the file name
  outside:
  return owl_create_str(ptr, size, file -> path -> enc);
}

// insert bytes in a file
owl_byte * owl_insert_bytes_file(owl_file * file, owl_umax pos, owl_str * bytes)
{
  // checking the output of this function
  if (owl_insert_str_chs(bytes, pos, file -> data) == owl_false)
    return NULL;
  return file -> data -> arr -> ptr + pos + bytes -> arr -> size;
}

// remove bytes in a file
owl_umax owl_remove_bytes_file(owl_file * file, owl_umax pos, owl_umax rm_size)
{
  // checking the output of this function
  if (owl_delete_str_chs(file -> data, pos, rm_size) == owl_false)
    return 0;
  return rm_size;
}

// append the contents of a file to another file
owl_bool owl_append_to_file(owl_file * file, owl_file * append)
{
  // reuse owl_insert_bytes_file()
  if (owl_insert_bytes_file(file, file -> data -> arr -> size, append -> data) == NULL)
    return owl_false;
  return owl_true;
}

// save a file in memory on system
owl_bool owl_save_file(owl_file * file, owl_str * path)
{
  // check params
  owl_mem_space * tmp = NULL;
  FILE * f = NULL;
  if (owl_check_file_all(file, sizeof(owl_file)) == owl_false
      || owl_check_str_all(path, sizeof(owl_str)) == owl_false)
    return owl_false;
  
  // create a duplicate of path with a 0x00 
  // character at the end of path just in case
  // ^ for stdio.h functions, dirty trick
  tmp = owl_create_mem_space(path -> arr -> size + 1);
  if (tmp == NULL) goto err;
  owl_copy_bytarr(path -> arr, tmp);
  
  // save the file
  f = fopen((void *) tmp -> ptr, "wb");
  if (f == NULL) goto err;
  for (owl_umax i = 0; i < file -> data -> arr -> size; i++)
    if (fputc((file -> data -> arr -> ptr)[i], f) == EOF)
      goto err;
  
  // done!
  owl_free_mem_space(tmp);
  fclose(f);
  return owl_true;
  err:
  owl_free_mem_space(tmp);
  if (f != NULL) fclose(f);
  return owl_false;
}

// to get a string out of a file
owl_str * owl_get_str_file(owl_file * file, owl_umax pos, owl_umax size, owl_wchar_enc enc)
{
  // check params
  if (owl_check_file_all(file, sizeof(owl_file)) == owl_false
      || pos + size > file -> data -> arr -> size
      || size == 0
      || owl_check_wchar_enc(enc) == owl_false)
    return NULL;
  
  // return the string if valid
  return owl_create_str(file -> data -> arr -> ptr + pos, size, enc);    
}

// to get a unsigned int written as characters out of a file ()
owl_int_op_result owl_get_ch_uint_file(owl_file * file, owl_umax pos, owl_wchar_enc enc)
{
  // check params
  if (owl_check_file_all(file, sizeof(owl_file)) == owl_false
      || owl_check_wchar_enc(enc) == owl_false)
    return (owl_int_op_result) {{0}, owl_false};
  
  // return the number if valid
  owl_umax result = owl_check_str_as_uint(file -> data, file -> data -> arr -> ptr + pos);
  if (result == 0) return (owl_int_op_result) {{0}, owl_false};
  result = owl_get_str_as_uint(file -> data, file -> data -> arr -> ptr + pos);
  return (owl_int_op_result) {{result}, owl_true};
}

// to get a signed int written as characters out of a file
owl_int_op_result owl_get_ch_sint_file(owl_file * file, owl_umax pos, owl_wchar_enc enc)
{
  // check params
  if (owl_check_file_all(file, sizeof(owl_file)) == owl_false
      || owl_check_wchar_enc(enc) == owl_false)
    return (owl_int_op_result) {{0}, owl_false};
  
  // return the number if valid
  owl_umax size = owl_check_str_as_sint(file -> data, file -> data -> arr -> ptr + pos);
  if (size == 0) return (owl_int_op_result) {{0}, owl_false};
  owl_smax result = owl_get_str_as_sint(file -> data, file -> data -> arr -> ptr + pos);
  return (owl_int_op_result) {{result}, owl_true};
}

// to get a float written as characters out of a file
owl_float_op_result owl_get_ch_float_file(owl_file * file, owl_umax pos, owl_wchar_enc enc)
{
  // check params
  if (owl_check_file_all(file, sizeof(owl_file)) == owl_false
      || owl_check_wchar_enc(enc) == owl_false)
    return (owl_float_op_result) {0.0f, owl_false};
  
  // return the number if valid
  owl_umax size = owl_check_str_as_float(file -> data, file -> data -> arr -> ptr + pos);
  if (size == 0) return (owl_float_op_result) {0.0f, owl_false};
  owl_fmax result = owl_get_str_as_uint(file -> data, file -> data -> arr -> ptr + pos);
  return (owl_float_op_result) {result, owl_check_float(result)};
}

// get an unsigned integer encoded in bits
owl_int_op_result owl_get_enc_uint_file(owl_file * file, owl_umax pos, owl_umax left_pad, owl_umax num_bits)
{
  // check params
  owl_umax size = ((left_pad + num_bits) / OWL_BYTE_BITS)
                  + ((left_pad + num_bits) % OWL_BYTE_BITS != 0) ? 1 : 0;
  if (owl_check_file_all(file, sizeof(owl_file)) == owl_false
      || pos + size > file -> data -> arr -> size)
    return (owl_int_op_result) {{0}, owl_false};
  
  // return the integer
  owl_bitarr * tmp = owl_create_bitarr(file -> data -> arr -> ptr + pos, size, left_pad, num_bits);
  owl_int_op_result result = owl_get_uint(tmp);
  owl_free_bitarr(tmp);
  return result;
}

// get an std unsigned integer encoded in bits
owl_int_op_result owl_get_enc_std_uint_file(owl_file * file, owl_umax pos, 
                                            owl_umax num_bits, owl_endian endian)
{
  // check params
  owl_umax size = (num_bits / OWL_BYTE_BITS) + (num_bits % OWL_BYTE_BITS != 0) ? 1 : 0;
  if (owl_check_file_all(file, sizeof(owl_file)) == owl_false
      || pos + size > file -> data -> arr -> size)
    return (owl_int_op_result) {{0}, owl_false};
  
  // return the integer
  owl_bitarr * tmp = owl_create_bitarr(file -> data -> arr -> ptr + pos, size, 0, num_bits);
  owl_int_op_result result = owl_get_std_uint(tmp, endian);
  owl_free_bitarr(tmp);
  return result;
}

// get an signed integer encoded in bits
owl_int_op_result owl_get_enc_sint_file(owl_file * file, owl_umax pos, owl_umax left_pad, owl_umax num_bits)
{
  // check params
  owl_umax size = ((left_pad + num_bits) / OWL_BYTE_BITS)
                  + ((left_pad + num_bits) % OWL_BYTE_BITS != 0) ? 1 : 0;
  if (owl_check_file_all(file, sizeof(owl_file)) == owl_false
      || pos + size > file -> data -> arr -> size)
    return (owl_int_op_result) {{0}, owl_false};
  
  // return the integer
  owl_bitarr * tmp = owl_create_bitarr(file -> data -> arr -> ptr + pos, size, left_pad, num_bits);
  owl_int_op_result result = owl_get_sint(tmp);
  owl_free_bitarr(tmp);
  return result;
}

// get an std signed integer encoded in bits
owl_int_op_result owl_get_enc_std_sint_file(owl_file * file, owl_umax pos, 
                                            owl_umax num_bits, owl_endian endian)
{
  // check params
  owl_umax size = (num_bits / OWL_BYTE_BITS) + (num_bits % OWL_BYTE_BITS != 0) ? 1 : 0;
  if (owl_check_file_all(file, sizeof(owl_file)) == owl_false
      || pos + size > file -> data -> arr -> size)
    return (owl_int_op_result) {{0}, owl_false};
  
  // return the integer
  owl_bitarr * tmp = owl_create_bitarr(file -> data -> arr -> ptr + pos, size, 0, num_bits);
  owl_int_op_result result = owl_get_std_sint(tmp, endian);
  owl_free_bitarr(tmp);
  return result;
}

// get a float encoded in bits
owl_float_op_result owl_get_enc_float_file(owl_file * file, owl_umax pos, owl_umax left_pad, owl_umax num_bits)
{
  // check params
  owl_umax size = ((left_pad + num_bits) / OWL_BYTE_BITS)
                  + ((left_pad + num_bits) % OWL_BYTE_BITS != 0) ? 1 : 0;
  if (owl_check_file_all(file, sizeof(owl_file)) == owl_false
      || pos + size > file -> data -> arr -> size)
    return (owl_float_op_result) {0, owl_false};
  
  // return the integer
  owl_bitarr * tmp = owl_create_bitarr(file -> data -> arr -> ptr + pos, size, left_pad, num_bits);
  owl_float_op_result result = owl_get_float(tmp);
  owl_free_bitarr(tmp);
  return result;
}

// get a std float encoded in bits
owl_float_op_result owl_get_enc_std_float_file(owl_file * file, owl_umax pos, 
                                               owl_umax num_bits, owl_endian endian)
{
  // check params
  owl_umax size = (num_bits / OWL_BYTE_BITS) + (num_bits % OWL_BYTE_BITS != 0) ? 1 : 0;
  if (owl_check_file_all(file, sizeof(owl_file)) == owl_false
      || pos + size > file -> data -> arr -> size)
    return (owl_float_op_result) {0, owl_false};
  
  // return the integer
  owl_bitarr * tmp = owl_create_bitarr(file -> data -> arr -> ptr + pos, size, 0, num_bits);
  owl_float_op_result result = owl_get_std_float(tmp, endian);
  owl_free_bitarr(tmp);
  return result;
}
